Skip to content

feat: bypass proxy for private/LAN IPs in management api-call#1960

Open
thebtf wants to merge 1 commit intorouter-for-me:mainfrom
thebtf:fix/no-proxy-private-ips
Open

feat: bypass proxy for private/LAN IPs in management api-call#1960
thebtf wants to merge 1 commit intorouter-for-me:mainfrom
thebtf:fix/no-proxy-private-ips

Conversation

@thebtf
Copy link
Contributor

@thebtf thebtf commented Mar 8, 2026

Summary

  • When a global proxy is configured, the management api-call handler routes all requests through it — including requests to LAN targets (e.g. LM Studio at 172.x.x.x). Since proxies typically cannot reach private networks, these requests time out.
  • The Management UI "Pick Models from /models" feature becomes unusable for any LAN-hosted provider when a proxy is configured.
  • Adds isNoProxyHost() that detects RFC 1918, loopback, and link-local addresses and skips the proxy for them.
  • Only affects the management api-call handler; executor proxy logic is unchanged.

Default bypass ranges

127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16, ::1/128, fc00::/7, fe80::/10

Files changed

  • internal/api/handlers/management/api_tools.go

Test plan

  • Configure global proxy in config.yaml
  • Use Management UI "Pick Models" with a LAN target (e.g. http://192.168.1.100:1234/v1/models)
  • Verify models are fetched successfully (no timeout)
  • Verify external targets still route through proxy

When a global proxy is configured, the management api-call handler
routes ALL requests through it — including requests to LAN targets
(e.g. LM Studio at 172.x.x.x). Since proxies typically cannot reach
private networks, these requests time out.

Add isNoProxyHost() that detects RFC 1918, loopback, and link-local
addresses and skips the proxy for them. Only affects the management
api-call handler; executor proxy logic is unchanged.

Default bypass ranges: 127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12,
192.168.0.0/16, 169.254.0.0/16, ::1/128, fc00::/7, fe80::/10.
@chatgpt-codex-connector
Copy link

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request enhances network request handling by introducing intelligent proxy bypass logic. It addresses a critical usability issue where the management API's api-call handler, when a global proxy is active, would incorrectly route requests to internal network resources through the proxy, leading to connection timeouts. By detecting private and local IP ranges, the system can now correctly route these requests directly, ensuring seamless interaction with LAN-hosted services like model providers in the Management UI.

Highlights

  • Proxy Bypass for Private/LAN IPs: Implemented a mechanism to bypass the configured global proxy for requests targeting private, loopback, and link-local IP addresses within the management api-call handler. This resolves timeouts experienced when accessing LAN-hosted services (e.g., LM Studio) through a proxy.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • internal/api/handlers/management/api_tools.go
    • Modified the apiCallTransport function signature to accept a targetHost parameter.
    • Introduced a new isNoProxyHost function to determine if a given host falls within predefined private IP ranges.
    • Defined defaultNoProxyCIDRs containing common private, loopback, and link-local CIDR blocks.
    • Updated existing calls to apiCallTransport to pass the relevant host information, enabling conditional proxy bypass.
Activity
  • No human activity has been recorded for this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a feature to bypass the configured proxy for requests to private/LAN IP addresses in the management API call handler. The security review agent noted that no specific changes were provided for review, preventing a detailed security critique. The implementation correctly modifies the apiCallTransport function to check the target host and conditionally skip proxying, and the changes in refreshGeminiOAuthAccessToken and refreshAntigravityOAuthAccessToken ensure external OAuth requests continue to use the proxy. A suggestion has been made to improve the efficiency and robustness of the new isNoProxyHost function by pre-parsing the CIDR ranges at startup.

Comment on lines +637 to +668
var defaultNoProxyCIDRs = []string{
"127.0.0.0/8",
"10.0.0.0/8",
"172.16.0.0/12",
"192.168.0.0/16",
"169.254.0.0/16",
"::1/128",
"fc00::/7",
"fe80::/10",
}

// isNoProxyHost returns true when targetHost should bypass the proxy.
func isNoProxyHost(targetHost string) bool {
host := targetHost
if h, _, err := net.SplitHostPort(host); err == nil {
host = h
}
ip := net.ParseIP(host)
if ip == nil {
return false
}
for _, cidr := range defaultNoProxyCIDRs {
_, network, err := net.ParseCIDR(cidr)
if err != nil {
continue
}
if network.Contains(ip) {
return true
}
}
if h != nil && h.cfg != nil {
if proxyStr := strings.TrimSpace(h.cfg.ProxyURL); proxyStr != "" {
proxyCandidates = append(proxyCandidates, proxyStr)
return false
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current implementation of isNoProxyHost parses the hardcoded CIDR strings on every function call. This is inefficient as it can be done once at application startup.

Additionally, if one of the hardcoded CIDRs is invalid, net.ParseCIDR will return an error which is silently ignored with continue. For hardcoded values, it's better to fail fast at startup if they are invalid.

I suggest pre-parsing the CIDRs into a slice of *net.IPNet during initialization. This improves performance and makes the logic more robust.

var defaultNoProxyCIDRs = []string{
	"127.0.0.0/8",
	"10.0.0.0/8",
	"172.16.0.0/12",
	"192.168.0.0/16",
	"169.254.0.0/16",
	"::1/128",
	"fc00::/7",
	"fe80::/10",
}

var parsedNoProxyCIDRs = func() []*net.IPNet {
	cidrs := make([]*net.IPNet, 0, len(defaultNoProxyCIDRs))
	for _, cidr := range defaultNoProxyCIDRs {
		_, network, err := net.ParseCIDR(cidr)
		if err != nil {
			// Panic on startup if hardcoded CIDRs are invalid.
			panic(fmt.Sprintf("invalid hardcoded CIDR in defaultNoProxyCIDRs: %q: %v", cidr, err))
		}
		cidrs = append(cidrs, network)
	}
	return cidrs
}()

// isNoProxyHost returns true when targetHost should bypass the proxy.
func isNoProxyHost(targetHost string) bool {
	if targetHost == "" {
		return false
	}
	host := targetHost
	if h, _, err := net.SplitHostPort(host); err == nil {
		host = h
	}
	ip := net.ParseIP(host)
	if ip == nil {
		return false
	}
	for _, network := range parsedNoProxyCIDRs {
		if network.Contains(ip) {
			return true
		}
	}
	return false
}

Copy link

@xkonjin xkonjin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

Overall: ✅ Approvable — correct approach, one robustness suggestion.

What's good

  • Bypassing proxy for RFC 1918 / loopback / link-local addresses is the right fix. These ranges are unreachable via most external proxies and routing them through one silently fails.
  • Passing targetHost explicitly rather than re-parsing inside the transport is clean.
  • Correctly passing "" for OAuth refresh calls (so they always use proxy — OAuth endpoints are always external).
  • IPv6 ranges (::1/128, fc00::/7, fe80::/10) included alongside IPv4 — thorough.

Issue: CIDR parsing on every request

isNoProxyHost calls net.ParseCIDR inside a loop on every invocation. With 8 CIDR strings this is fast but unnecessary work. Pre-parse defaultNoProxyCIDRs to []*net.IPNet at init time (package-level var with init() or a sync.Once) and just iterate the pre-parsed networks. This is what gemini-code-assist flagged and it's worth doing.

Minor notes

  • isNoProxyHost only matches IP addresses — hostname-based LAN targets (e.g. mymachine.local, raspberrypi) return false and will be proxied. This is probably acceptable for now but worth documenting as a known limitation in a comment.
  • No test for isNoProxyHost itself. Given it's a security boundary (bypasses proxy), a small table-driven test covering private IPs, public IPs, IPv6, and hostnames would be valuable.

Copy link

@xkonjin xkonjin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice direction, but there is still a gap in the no-proxy detection that will break common LAN setups.

isNoProxyHost() only bypasses the proxy when targetHost is already a literal IP (net.ParseIP(host) succeeds). For typical management traffic to local devices, the target is often a hostname that resolves to a private address (nas.local, router.lan, mDNS names, internal DNS names, etc.). In those cases this PR still sends the request through the proxy, which is exactly the path this change is trying to avoid.

Please either:

  • resolve hostnames before the CIDR check, or
  • document and test that only literal IP targets are supported here.

I’d strongly prefer the former, plus a regression test covering a hostname that resolves to RFC1918 space.

Copy link
Collaborator

@luispater luispater left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary:
The overall direction makes sense, but there is still one correctness gap before this is safe to merge.

Key findings:

  • Blocking: isNoProxyHost() only recognizes literal IPs. Requests to http://localhost:... still go through the configured proxy because net.ParseIP("localhost") returns nil, and bracketed IPv6 loopback URLs such as http://[::1]/... are missed as well. That means a common local-provider setup is still broken even though the PR claims to bypass loopback/LAN targets.
  • Non-blocking: there is no regression coverage for the new bypass logic, so this kind of hole is easy to miss in CI.

Test plan:

  • go test ./...

Follow-up:

  • Please normalize the host with URL hostname semantics (or explicitly special-case localhost) and add regression coverage for localhost, literal private IPv4, and IPv6 loopback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants